package configs

import (
    "github.com/spf13/viper"
	"gorm.io/gorm"
	"fmt"
	"log"
	"time"
	"os"
	"strings"
	"path/filepath"
	"net/url"
	"gorm.io/driver/mysql"
	"github.com/patrickmn/go-cache"
	"github.com/robfig/cron/v3"
	"github.com/ArtisanCloud/PowerWeChat/v3/src/kernel/response"
    "github.com/ArtisanCloud/PowerWeChat/v3/src/miniProgram"
	"github.com/ArtisanCloud/PowerWeChat/v3/src/payment"
)

var (
    Config  *viper.Viper
    MyURL   string
    DB      *gorm.DB
    Cron    *cron.Cron     //定时调度器
    // 配置缓存，5分钟为清理间隔，10分钟为最大过期时间
	CRdb = cache.New(5*time.Minute, 10*time.Minute)
	PaymentApps = map[string]*payment.Payment{}     //存储小程序PaymentApp和公众号实例GzhPaymentApp的支付实例
    MiniProgramApp *miniProgram.MiniProgram         //小程序
)

type WeChatPaymentConfig struct {
    AppID        string `mapstructure:"appid"`         // 小程序AppID
    AppSecret    string `mapstructure:"secret"`        // 小程序密钥
    MchID        string `mapstructure:"MchID"`         // 商户号
    MchApiV3Key  string `mapstructure:"MchApiV3Key"`   // API V3 密钥
    SerialNo     string `mapstructure:"SerialNo"`      // 证书序列号
    CertDir      string `mapstructure:"cert_dir"`      // 证书存放目录
    NotifyURL    string `mapstructure:"-"`             // 回调地址（动态生成，无需配置）
}

// 启动初始化
func InitConfig() {
    fmt.Println("初始化配置中...")
    CreadDir(filepath.Join(".", "temp"))
    CreadDir(filepath.Join("uploads", "excel"))
    CreadDir(filepath.Join("uploads", "blog"))
    CreadDir(filepath.Join("uploads", "zsmb"))
	CreadDir(filepath.Join("uploads", "zs"))
	CreadDir(filepath.Join("uploads", "fonts"))
    Config = viper.New()
    Config.SetConfigName("config")
    Config.SetConfigType("yaml")
    Config.AddConfigPath(".")
    if err := Config.ReadInConfig(); err != nil {
        panic("配置文件初始化失败")
    }
    rawURL := Config.GetString("server.url")
    normalizedURL, err := processURL(rawURL)
    if err != nil {
        panic("配置文件url错误")
    }
    if normalizedURL != rawURL {
        Config.Set("server.url", normalizedURL)
        if err := Config.WriteConfig(); err != nil {
            panic("配置更新失败")
        }
    }
    MyURL = normalizedURL
    Writejs(MyURL)                                  // 更新静态js文件中的网址
    initDB()                                        // 初始化数据库
    initcron()                                      // 初始化定时任务
    IsStart()                                       // 初始化序列号 
    // 支付初始化（仅在配置存在时执行）
    wechatConfig := Config.Sub("wechat")
    if wechatConfig != nil {
        InitPaymentApp(wechatConfig)
    } else {
        log.Println("警告: 未找到微信支付配置，相关功能将不可用")
    }
}

// 初始化数据库连接
func initDB() {
    my := Config.Sub("mysql")
    if my == nil {
        log.Fatalf("MySQL 配置缺失")
    }
    dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=%t&loc=Local",
        my.GetString("username"),
        my.GetString("password"),
        my.GetString("host"),
        my.GetInt("port"),
        my.GetString("database"),
        my.GetString("charset"),
        my.GetBool("parseTime"),
    )
    var err error
    DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        log.Fatalf("数据库连接失败: %v", err)
    }
    //DB.Migrator().DropTable(&User{}) //删除原表重置
    // 数据库迁移
    err = DB.AutoMigrate(&User{}, &FormSchema{}, &Template{}, &PicTemplate{}, &Blog{}, &FileRecord{}, &DocGenTask{}, &ProcessInstance{}, &ApprovalRecord{}, &PicTemplate{}, &Purchase{}, &Message{}, &HomeTemplate{}, &Order{}, &MessageTemplate{})
    if err != nil {
        log.Fatalf("数据库迁移失败: %v", err)
    }
    // 创建管理员用户
    createAdminIfNeeded()
}

//初始化定时器
func initcron(){
    Cron = cron.New() // 创建一个新的cron调度器
	//Cron 表达式通常包括五个字段，分别表示分钟、小时、日、月和星期几。每个字段可以包含特定的值或特殊字符来控制任务的执行时间
	Cron.AddFunc("0 1 * * *", func() {
		fmt.Println("每天凌晨1点清理/uploads/excel目录下文件")
		DeleteFilesInDirectory("./uploads/excel/")
		fmt.Println("每天凌晨1点检查序列号有效性")
        CheckSNValidity()
	})
	Cron.Start()
}

//创建内置管理员
func createAdminIfNeeded() {
	var count int64
	err := DB.Model(&User{}).Count(&count).Error
	if err != nil {
		log.Fatalf("检查 admin 账号失败: %v", err)
	}
	if count == 0 {
		password, _ := HashPassword("123456")
		admin := User{
			Username: "admin",
			Password: password,
			Name:     "admin",
			Role:     "admin",
			Email:    "2554509967@qq.com",
		}
		if err := DB.Create(&admin).Error; err != nil {
			log.Fatalf("创建 admin 账号失败: %v", err)
		}
		log.Println("系统内置 admin 账号创建成功")
	}
}

// 更新配置文件
func UpdateEnvFile(key, value string) error {
    if key == "server.url" {
        normalizedURL, err := processURL(value)
        if err != nil {
            return fmt.Errorf("url验证失败: %w", err)
        }
        value = normalizedURL
    }
    Config.Set(key, value)
    if err := Config.WriteConfig(); err != nil {
        return fmt.Errorf("配置持久化失败: %w", err)
    }
    if key == "server.url" {
        MyURL = value
        Writejs(MyURL)      //网址修改了要更新到js文件
    }
    return nil
}

//更新静态js文件中的网址
func Writejs(my string) {
    baseURL := strings.TrimSuffix(my, "/")
    txt := fmt.Sprintf("const globalData = { localBaseUrl: '%s/v1/' };", baseURL)
    file := filepath.Join("templates", "static", "h5config.js")
    err := os.WriteFile(file, []byte(txt), 0644)
    if err != nil {
        log.Fatalf("h5config.js 写入失败: %v", err)
    }
}

// 统一处理 URL 验证和标准化
func processURL(raw string) (string, error) {
    if raw == "" {
        return "", fmt.Errorf("url不能为空")
    }
    if !hasScheme(raw) {
        return "", fmt.Errorf("url必须以http://或htts://开头")
    }
    // 解析验证
    u, err := url.ParseRequestURI(raw)
    if err != nil {
        return "", fmt.Errorf("解析失败: %w", err)
    }
    if u.Host == "" {
        return "", fmt.Errorf("缺少有效主机名")
    }
    // 路径标准化
    u.Path = strings.TrimRight(u.Path, "/")
    if u.Path == "" {
        u.Path = "/"
    }
    return u.String(), nil
}

// 检查是否包含协议头
func hasScheme(s string) bool {
    return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
}

// 分页: page当前页, pageSize每页显示条数
func Paginate(page, pageSize int) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		if page == 0 {
			page = 1
		}
		switch {
		case pageSize > 10000:
			pageSize = 10000
		case pageSize <= 0:
			pageSize = 10
		}
		// Offset要跳过的记录数量
		offset := (page - 1) * pageSize
		return db.Offset(offset).Limit(pageSize)
	}
}

// 创建消息助手
func CreateMessage(tx *gorm.DB, userID uint, title, content string) error {
    if tx == nil {
        tx = DB
    }
    msg := Message{
        UserID:  userID,
        Title:   title,
        Content: content,
    }
    return tx.Create(&msg).Error
}

// 支付实例初始化入口
func InitPaymentApp(wechatConfig *viper.Viper) {
    if wechatConfig == nil {
        log.Println("警告: 微信支付配置缺失，跳过初始化")
        return
    }
    var config WeChatPaymentConfig
    // 将 Viper 配置绑定到结构体
    if err := wechatConfig.Unmarshal(&config); err != nil {
        log.Printf("解析微信支付配置失败: %v", err)
        return
    }
    if err := InitMiniProgramApp(&config); err != nil {
        log.Println("小程序初始化失败", err)
    }
    config.NotifyURL = fmt.Sprintf("%sv1/wxnotify", MyURL) // 动态设置回调地址（确保 MyURL 已初始化）
    if err := createPaymentApp(&config, "miniProgram"); err != nil {
        log.Printf("小程序支付初始化失败: %v", err)
    }
}

//小程序实例初始化
func InitMiniProgramApp(config *WeChatPaymentConfig) error {
	if(config.AppID != "" && config.AppSecret != ""){
	    var err error
        MiniProgramApp, err = miniProgram.NewMiniProgram(&miniProgram.UserConfig{
            AppID:     config.AppID,
            Secret:    config.AppSecret,
            HttpDebug: true,
            Log: miniProgram.Log{}, //不记录log
        })
        return err
	}
	return nil
}

// 创建支付实例（通用逻辑）
func createPaymentApp(config *WeChatPaymentConfig, appType string) error {
    // 1. 校验必填参数
    if config.AppID == "" || config.MchID == "" || config.MchApiV3Key == "" || config.SerialNo == "" {
        return fmt.Errorf("%s 配置不完整", appType)
    }
    // 2. 计算证书路径
    certPath, keyPath, err := getCertPath(config.CertDir)
    if err != nil {
        return fmt.Errorf("计算证书路径失败: %w", err)
    }
    // 3. 检查证书文件是否存在
    if !CheckFileIsExist(certPath) || !CheckFileIsExist(keyPath) {
        return fmt.Errorf("%s 证书文件缺失，请检查目录: %s", appType, filepath.Dir(certPath))
    }
	app, err := payment.NewPayment(&payment.UserConfig{
        AppID:              config.AppID,       // 小程序的appid
        MchID:              config.MchID,       // 商户号 appID
        MchApiV3Key:        config.MchApiV3Key, // 微信V3接口调用必填
        Key:                "",                 // 微信V2接口调用必填
        CertPath:           certPath,           // 商户后台支付的Cert证书路径
        KeyPath:            keyPath,            // 商户后台支付的Key证书路径
        SerialNo:           config.SerialNo,    // 商户支付证书序列号
        CertificateKeyPath: "",                 // 微信支付平台证书的Key证书路径,m微信V3,[选填]
        WechatPaySerial:    "",                 // 微信支付平台证书序列号,微信V3，[选填]
        RSAPublicKeyPath:   "",                 // 商户支付证书序列号,微信V2，[选填]
        SubMchID:           "",                 // 服务商平台下的子商户号Id，[选填]
        SubAppID:           "",                 // 服务商平台下的子AppId，[选填]
		NotifyURL:          config.NotifyURL,   //回调地址
		ResponseType:       response.TYPE_MAP,
		Log: payment.Log{
			Level: "debug",
			File:  "./wechat.log",
		},
		Http: payment.Http{
			Timeout: 30.0,
			BaseURI: "https://api.mch.weixin.qq.com",
		},
		HttpDebug: true,
		Debug:     false,
		//Debug:     true,
	})
    if err != nil {
        return (err)
    }
    PaymentApps[appType] = app //存储不同实例
    return nil
}

func getCertPath(configDir string) (certPath, keyPath string, err error) {
    if configDir == "" {
        configDir = filepath.Join("wechat", "wxCert") // 默认路径
    }
    exePath, err := os.Executable() // 获取可执行文件所在目录
    if err != nil {
        return "", "", fmt.Errorf("获取可执行文件路径失败: %w", err)
    }
    exeDir := filepath.Dir(exePath)
    // 计算证书目录（证书存放在可执行文件目录下的相对路径）
    certDir := filepath.Join(exeDir, configDir)
    certPath = filepath.Join(certDir, "apiclient_cert.pem")
    keyPath = filepath.Join(certDir, "apiclient_key.pem")
    return certPath, keyPath, nil
}